#pragma once



#include <type_traits>
#include <limits>
#include "utility.hpp"


namespace cpp
{
	template<class T>
	class bitset
	{
		static_assert(std::is_unsigned<T>::value, "must be unsigned type");
		enum { _Bits = sizeof(T) * CHAR_BIT };
	public:
		constexpr bitset() noexcept
			:_value()
		{}
		constexpr bitset(T value) noexcept
			:_value(value)
		{}

		 bitset& set() noexcept
		{
			_value = ~T(0);
			return *this;
		}

		bitset& set(size_t pos, bool value = true) 
		{
			if(_Bits <= pos)
			{
				throw_out_of_range();
			}
			if(value)
			{
				_value |= (T)1 << pos;
			}
			else
			{
				_value &= ~((T)1 << pos);
			}
			return *this;
		}

		bitset& reset() noexcept
		{
			_value = T(0);
			return *this;
		}

		bitset& reset(size_t pos)
		{
			if(_Bits <= pos)
			{
				throw_out_of_range();
			}
			_value &= ~((T)1 << pos);
			return *this;
		}

		template< size_t h, size_t l, class R = T >
		R get_bits() const noexcept
		{
			static_assert(l < h &&  h < _Bits, "out of range");
			static_assert(std::is_unsigned<R>::value, "must be unsigned type");

			return get_bits<R>(make_index_range<l, h + 1>());
		}

		template<size_t h, size_t l>
		void set_bits(T const&u) noexcept
		{
			static_assert(l < h &&  h < _Bits, "out of range");

			set_bits(u, make_index_range<l, h + 1>());
		}

		constexpr bool operator[](std::size_t pos) const noexcept
		{
			return (_value & (T)1 << pos) != 0;
		}

		bool test(size_t pos) const
		{
			if(_Bits <= pos)
			{
				throw_out_of_range();
			}

			return (_value & (T)1 << pos) != 0;
		}

		constexpr std::size_t size() const noexcept
		{
			return _Bits;
		}

		constexpr T& value() noexcept
		{
			return _value;
		}
		constexpr T value() const noexcept
		{
			return _value;
		}

		constexpr void set_value(T value) noexcept
		{
			_value = value;
		}

	private:

		void throw_out_of_range()
		{
			throw std::out_of_range("invalid bitset position");
		}

		template< T... vals >
		struct add;

		template<T u, T... vals >
		struct add < u, vals... >
		{
			static const T  value = u + add < vals... >::value;
		};

		template<>
		struct add < >
		{
			static const T  value = 0;
		};

		template<class R, size_t... pos>
		R get_bits(index_sequence < pos...> const&) const noexcept
		{ 
			// Fold-expessions: T mask = ... + (1 << pos);

			T mask = add< 1 << pos ... > ::value;

			auto val = _value&mask;

			return val >> index_sequence < pos...>::front;
		}

		template<class U, size_t... pos>
		void set_bits(U const& u, index_sequence < pos...> const&) noexcept
		{
			auto val = u << index_sequence < pos...> ::front;

			const T mask = add<  1 << pos ... > ::value;

			auto val1 = val&mask;

			auto val2 = _value& ~mask;

			_value = val1 | val2;
		}
	private:
		T _value;
	};
}

